package com.smedi.ismedi.authoritymanagement.core.domain.service.impl;

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import cn.hutool.log.Log;
import com.smedi.ismedi.authoritymanagement.core.domain.repository.TermRepository;
import com.smedi.ismedi.authoritymanagement.core.domain.service.TermDomainService;
import com.smedi.ismedi.authoritymanagement.core.infrastructure.annotation.PrimaryKey;
import com.smedi.ismedi.authoritymanagement.core.infrastructure.annotation.TermKey;
import com.smedi.ismedi.authoritymanagement.core.infrastructure.constant.CustomError;
import com.smedi.ismedi.web.starter.exception.LogicException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author: xulinjun
 * @Date: 2021-7-15 16:35
 */
@Slf4j
@Service
@AllArgsConstructor
public class TermDomainServiceImpl implements TermDomainService {
    private final Map<String, TermRepository> termRepositories = new ConcurrentHashMap<>();

    private final String split = "-";

    @Autowired
    public void termRepositoryInterface(Map<String, TermRepository> termRepositories) {
        this.termRepositories.clear();
        termRepositories.forEach(this.termRepositories::put);
    }

    /**
     * 生成供前端使用的 reference key，用于前端控制
     *
     * @return
     */
    private <T> String generateTermKey(T t) {
        Snowflake snowflake = IdUtil.createSnowflake(Long.valueOf(new Random().nextInt(20) + 1), Long.valueOf(new Random().nextInt(30) + 1));
        return t.getClass().getSimpleName() + split + snowflake.nextId();
    }

    /**
     * 获取类、继承父类中所有定义的字段
     *
     * @param t
     * @param <T>
     * @return
     */
    private <T> List<Field> getDeclaredFields(T t) {
        Class<? extends Object> clazz = t.getClass();

        List<Field> fields = new ArrayList<>();
        while (clazz != null && !clazz.getName().equals(Object.class.getName())) {
            if (fields.size() <= 0) {
                fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
            } else {
                Field[] array = clazz.getDeclaredFields();
                for (Field target : array) {
                    if (!fields.stream().filter(field -> field.getName().equals(target.getName())).findAny().isPresent()) {
                        fields.add(target);
                    }
                }
            }
            clazz = clazz.getSuperclass();
        }

        return fields;
    }

    /**
     * 获取当前实体的主键id
     *
     * @param t
     * @return
     */
    private <T> Long getPrimaryKey(T t) {
        // 判断当前类打了几个@PrimaryKey的注解
        int i = 0;
        Long primaryKey = null;

        List<Field> fields = getDeclaredFields(t);
        for (Field field : fields) {
            if (field.isAnnotationPresent(PrimaryKey.class)) {
                primaryKey = (Long) getValue(t, field.getName());
                i++;
            }
        }

        if (i == 1) {
            return primaryKey;
        } else {
            throw LogicException.raise(CustomError.PRIMARY_KEY_MISS);
        }
    }

    /**
     * 根据主键获取领域信息
     *
     * @param data
     * @param primaryKey
     * @param <T>
     * @return
     */
    private <T> T getDomainByPrimaryKey(T data, Long primaryKey) {
        T basic;
        // 判断通过哪个接口获取实体对象
        Class clazz = data.getClass();

        TermRepository termRepository = termRepositories.get(clazz.getSimpleName());
        if (termRepository == null) {
            throw LogicException.raise(CustomError.INVALID_CLASS);
        } else {
            basic = (T) termRepository.selectByTermId(primaryKey);
        }
        return basic;
    }

    /**
     * 保存领域模型
     *
     * @param basic
     * @param update
     * @param <T>
     * @return
     */
    private <T> T saveDomain(T basic, boolean update) {
        // 判断通过哪个接口获取实体对象
        Class clazz = basic.getClass();

        TermRepository termRepository = termRepositories.get(clazz.getSimpleName());
        if (termRepository == null) {
            throw LogicException.raise(CustomError.INVALID_CLASS);
        } else {
            basic = update ? (T) termRepository.doUpdate(basic) : (T) termRepository.doInsert(basic);
        }

        return basic;
    }

    /**
     * 获取对象数据
     *
     * @param t
     * @param targetField
     * @param <T>
     * @return
     */
    private <T> Object getValue(T t, String targetField) {
        try {
            // Lombok中的Access的chain = true 会使类反射出错，故用内省
            PropertyDescriptor[] properties = Introspector.getBeanInfo(t.getClass()).getPropertyDescriptors();
            for (PropertyDescriptor propDescriptor : properties) {
                if (targetField.equals(propDescriptor.getName())) {
                    Object value = propDescriptor.getReadMethod().invoke(t);
                    return value;
                }
            }

            return null; // 没有找到则返回null;
        } catch (IntrospectionException e) {
            throw LogicException.raise(CustomError.UN_HANDLER_EXCEPTION, e);
        } catch (IllegalAccessException e) {
            throw LogicException.raise(CustomError.UN_HANDLER_EXCEPTION, e);
        } catch (InvocationTargetException e) {
            throw LogicException.raise(CustomError.UN_HANDLER_EXCEPTION, e);
        }
    }

    /**
     * 对象参数赋值
     *
     * @param t
     * @param data
     * @param <T>
     */
    private <T> void doObjectAssigning(T t, T data, boolean update) {
        List<Field> fields = getDeclaredFields(t);
        try {
            for (Field field : fields) {
                field.setAccessible(true);
                // 新建的情况下，生成key数据，其他情况不保存
                if (field.isAnnotationPresent(TermKey.class)) {
                    if (!update) {
                        field.set(t, generateTermKey(t));
                    }
                } else {
                    field.set(t, getValue(data, field.getName()));
                }
            }
        } catch (IllegalAccessException e) {
            throw LogicException.raise(CustomError.UN_HANDLER_EXCEPTION, e);
        }
    }

    @Override
    public <T> T saveTerm(T data) {
        T basic;
        // 保存模式
        boolean update = false;
        // 获取主键
        Long primaryKey = getPrimaryKey(data);

        if (primaryKey != null) {
            update = true;
            basic = getDomainByPrimaryKey(data, primaryKey);
            if (basic == null) {
                throw LogicException.raise(CustomError.PERSON_NOT_EXIST);
            }
        } else {
            try {
                basic = (T) data.getClass().newInstance(); // 创建实例
            } catch (InstantiationException e) {
                throw LogicException.raise(CustomError.UN_HANDLER_EXCEPTION, e);
            } catch (IllegalAccessException e) {
                throw LogicException.raise(CustomError.UN_HANDLER_EXCEPTION, e);
            }
        }

        // 赋值基本属性的字段
        doObjectAssigning(basic, data, update);

        // 保存信息
        return saveDomain(basic, update);
    }

    @Override
    public <T> T queryTermByTermIdAndType(Long termId, String termType) {
        TermRepository termRepository = termRepositories.get(termType);
        if (termRepository == null) {
            throw LogicException.raise(CustomError.INVALID_CLASS);
        } else {
            T basic = termRepository.selectByTermId(termId);
            if (basic != null) {
                return basic;
            } else {
                throw LogicException.raise(CustomError.TERM_NOT_FOUND);
            }
        }
    }

    @Override
    public <T> T getAttribute(String termType, Long termId, String targetField) {
        TermRepository termRepository = termRepositories.get(termType);
        if (termRepository == null) {
            throw LogicException.raise(CustomError.INVALID_CLASS);
        } else {
            T t = termRepository.selectByTermId(termId);
            if (t != null) {
                PropertyDescriptor[] properties = new PropertyDescriptor[0];
                try {
                    properties = Introspector.getBeanInfo(t.getClass()).getPropertyDescriptors();
                    for (PropertyDescriptor propDescriptor : properties) {
                        if (targetField.equals(propDescriptor.getName())) {
                            return (T) propDescriptor.getReadMethod().invoke(t);
                        }
                    }
                } catch (IntrospectionException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }
}
